//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * serial-mhn.c: PXA250/210 serial port driver
 *
 * Copyright (C) 2002  Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Copyright (C) 2002  Jeff Sutherland <jeffs@accelent.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <arch.h>
#include <errno.h>
#include <serial.h>
#include <types.h>

#if defined USE_SERIAL1

# define SerialDATA  FFRBR
# define SerialFIFO  FFFCR
# define SerialLCR   FFLCR
# define SerialMCR   FFMCR
# define SerialLSR   FFLSR
# define SerialMSR   FFMSR
# define SerialIER   FFIER

#else /* defined USE_SERIAL1 */

#if defined USE_SERIAL2

# define SerialDATA  BTRBR
# define SerialFIFO  BTFCR
# define SerialLCR   BTLCR
# define SerialMCR   BTMCR
# define SerialLSR   BTLSR
# define SerialIER   BTIER
# define SerialMSR   BTMSR

#else /* defined USE_SERIAL2 */

#if defined USE_SERIAL3

/* using an Xscale CPU and not having any serial port defined is an
 * error, but because we're using the driver in the library we can't
 * bail out over here
 */

# define SerialDATA  STRBR
# define SerialFIFO  STFCR
# define SerialLCR   STLCR
# define SerialMCR   STMCR
# define SerialLSR   STLSR
# define SerialIER   STIER
# define SerialMSR   STMSR

#else
#error "No serial port defined USE_SERIAL1 .. USE_SERIAL3"

#endif /* defined USE_SERIAL3 */

#endif /* defined USE_SERIAL2 */

#endif /* defined USE_SERIAL1 */

/* flush serial input queue. returns 0 on success or negative error
 * number otherwise
 */
static int mhn_serial_flush_input(void)
{
    volatile u32 tmp;

    /* keep on reading as long as the receiver is not empty */
    while (SerialLSR & LSR_DR) {
        if (SerialLSR & (LSR_FE | LSR_PE | LSR_OE))
            return -ESERIAL;

        tmp = SerialDATA;
    }

    return 0;
}

/* flush output queue. returns 0 on success or negative error number
 * otherwise.  This only waits for space in the tx fifo, NOT until the fifo
 * has completely emptied.
 */
static int mhn_serial_flush_output(void)
{
    /* wait until the transmitter is no longer busy */
    while(SerialLSR & ~LSR_TDRQ) {
        ;
    }

    return 0;
}

/* initialise serial port at the request baudrate. returns 0 on
 * success, or a negative error number otherwise
 */
int mhn_serial_init(serial_baud_t baud)
{
    u32 divisor;

#ifdef USE_SERIAL1
#if 1
    *(unsigned int *)MFPR41_ADDR = 0x802; /* FFRXD */
    *(unsigned int *)MFPR42_ADDR = 0x802; /* FFTXD */
    *(unsigned int *)MFPR43_ADDR = 0x802; /* FFCTS */
    *(unsigned int *)MFPR44_ADDR = 0x802; /* FFDCD */
    *(unsigned int *)MFPR45_ADDR = 0x802; /* FFDSR */
    *(unsigned int *)MFPR46_ADDR = 0x802; /* FFRI */
    *(unsigned int *)MFPR47_ADDR = 0x802; /* FFDTR */
    *(unsigned int *)MFPR48_ADDR = 0x802; /* FFRTS */
#else
    /* set GPIO */
    set_GPIO_mode(GPIO97_FF_UART1_RXD);
    set_GPIO_mode(GPIO98_FF_UART1_TXD);
    set_GPIO_mode(GPIO99_FF_UART1_CTS);
    set_GPIO_mode(GPIO100_FF_UART1_DCD);
    set_GPIO_mode(GPIO101_FF_UART1_DSR);
    set_GPIO_mode(GPIO102_FF_UART1_RI);
    set_GPIO_mode(GPIO103_FF_UART1_DTR);
    set_GPIO_mode(GPIO104_FF_UART1_RTS);
#endif

    /* enable clock */
    CKENA |= CKENA_22_FFUART;
#endif

#ifdef USE_SERIAL2
#if 1
    *(unsigned int *)MFPR109_ADDR = 0x801; /* BTRTS */
    *(unsigned int *)MFPR110_ADDR = 0x801; /* BTRXD */
    *(unsigned int *)MFPR111_ADDR = 0x801; /* BTTXD */
    *(unsigned int *)MFPR112_ADDR = 0x801; /* BTCTS */
#else
    /* set GPIO */
    set_GPIO_mode(GPIO109_BT_UART2_RTS);
    set_GPIO_mode(GPIO110_BT_UART2_RXD);
    set_GPIO_mode(GPIO111_BT_UART2_TXD);
    set_GPIO_mode(GPIO112_BT_UART2_CTS);
#endif

    /* enable BTUART clock */
    CKENA |= CKENA_21_BTUART;
#endif

#ifdef USE_SERIAL3
#if 1
    *(unsigned int *)MFPR107_ADDR = 0x801; /* STTXD */
    *(unsigned int *)MFPR108_ADDR = 0x801; /* STRXD */
#endif
    CKENA |= CKENA_23_STUART;
#endif

    /* get correct divisor */
    switch (baud) {
        case baud_1200:
            divisor = 768;
            break;

        case baud_9600:
            divisor = 96;
            break;

        case baud_19200:
            divisor = 48;
            break;

        case baud_38400:
            divisor = 24;
            break;

        case baud_57600:
            divisor = 16;
            break;

        case baud_115200:
            divisor = 8;
            break;

        case baud_230400:
            divisor = 4;
            break;

        default:
            return -ERANGE;
    }

    /* switch receiver and transmitter off */
    SerialLCR = 0;
    SerialIER = 0;
    SerialFIFO = 0;

    /* Gain access to divisor latch */
    SerialLCR = LCR_WLS0 | LCR_WLS1 | LCR_DLAB;

    /* Load baud rate divisor in two steps, lsb, then msb of value */

    SerialDATA = divisor & 0xff;
    SerialIER = (divisor >> 8) & 0xff;

    /* set the port to sensible defaults (no break, no interrupts,
     * no parity, 8 databits, 1 stopbit, transmitter and receiver
     * enabled), reset dlab bit:
     */
    SerialLCR = LCR_WLS1 | LCR_WLS0;

    /* turn the receiver and transmitter back on */
    SerialIER = IER_UUE;

    return 0;
}

/* check if there is a character available to read. returns 1 if there
 * is a character available, 0 if not, and negative error number on
 * failure */
static int mhn_serial_poll(void)
{
    /* check for errors */
    if (SerialLSR & (LSR_FE | LSR_PE | LSR_OE))
        return -ESERIAL;

    if (SerialLSR & LSR_DR)
        return 1;
    else
        return 0;
}

/* read one character from the serial port. return character (between
 * 0 and 255) on success, or negative error number on failure. this
 * function is blocking */
static int mhn_serial_read(void)
{
    int rv;

    for (;;) {
        rv = mhn_serial_poll();

        if (rv < 0)
            return rv;

        if (rv > 0)
            return SerialDATA & 0xff;
    }
}

/* write character to serial port. return 0 on success, or negative
 * error number on failure. this function is blocking
 */
int mhn_serial_write(int c)
{
    /* wait for room in the transmit FIFO */
    while ((SerialLSR & LSR_TDRQ) == 0) {
    }

    SerialDATA = c & 0xff;

    return 0;
}

/* export serial driver */
serial_driver_t mhn_serial_driver = {
    init:   mhn_serial_init,
    read:   mhn_serial_read,
    write:  mhn_serial_write,
    poll:   mhn_serial_poll,
    flush_input:  mhn_serial_flush_input,
    flush_output: mhn_serial_flush_output
};
